// IMPLEMENTATION FOR THE CProfiler CLASS
// (c) Hernan Di Pietro 2003. && Samuel Batista 2008
/////////////////////////////////////////////////////////////////
#include "profiler.h"
#include <cstdio>
#include <fstream>

#pragma region Console Definitions
/* 

Author: 
Gregor S.
Modifications by Samuel Batista 2008

E-Mail: 
foxhunter@hlla.net

Date: 
08/20/2002 

Description: 
This class is a simple wrapper for some console functions.
The main goal is to keep it that simple so that even
beginners have no problems implementing it in their apps.
You can write debug outout or anything else you wish into
the console in different colors.

Usage:
Copy Console.cpp (this file) and Console.h into the directory
of your application. Insert them into the workspace if you wish.
Next, add the line '#include "Console.h"' into one of your source
files and add a member variable of tyle CConsole.

Copyright & disclaimer: 
Do want you want to do with this class: Modify it, extend it to your
needs. Use it in both Freeware, Shareware or commercial applications,
but make sure to leave these comments at the top of this source file 
intact and don't remove my name and E-mail address.

Modifications (by Samuel Batista 2008):
Changed the Output function to take in strings.
Removed some functions that were not required for the profiler.

*/

// macro for the Clear() function
#define ERR(bSuccess) { if(!bSuccess) return; }
// macro to check whether hConsole is valid
#define CHECK(hHandle) { if(hHandle == NULL) return; };


bool CConsole::Create(const char *szTitle)
{
	// Has console been already created?
	if(hConsole != NULL)
		return false;

	// Allocate a new console for our app
	if(!AllocConsole())
		return false;

	// Create the actual console
	hConsole = CreateFile("CONOUT$", GENERIC_WRITE|GENERIC_READ, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0);
	if(hConsole == INVALID_HANDLE_VALUE)
		return false;

	if(SetConsoleMode(hConsole, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT ) == 0)
		return false;

	DisableClose();
	// set the console title
	if(szTitle != NULL)
		SetConsoleTitle(szTitle);

	return true;
}

void CConsole::DisableClose()
{
	CHECK(hConsole);

	HWND hWnd = GetHWND();

	// disable the [x] button if we found our console
	if(hWnd != NULL)
	{
		HMENU hMenu = GetSystemMenu(hWnd,0);
		if(hMenu != NULL)
		{
			DeleteMenu(hMenu, SC_CLOSE, MF_BYCOMMAND);
			DrawMenuBar(hWnd);
		}
	}
}

void CConsole::Color(WORD wColor)
{
	CHECK(hConsole);

	// no color specified, reset to defaults (white font on black background)
	if(wColor != NULL)
		SetConsoleTextAttribute(hConsole, wColor );
	// change font and/or background color
	else
		SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); // white text on black bg
}

void CConsole::Output(string Output)
{
	DWORD		dwWritten;

	if(Output == "") 
		WriteConsole(hConsole,"\n",1,&dwWritten,0);
	else
	{
		int strLen = (int)Output.length();

		// if not parameter set, write a new line
		WriteConsole(hConsole,Output.c_str(),strLen,&dwWritten,0);
	}
}

void CConsole::SetTitle(const char *title)
{
	// self-explanatory
	SetConsoleTitle(title);
}

char* CConsole::GetTitle()
{
	// get the title of our console and return it
	static char szWindowTitle[256] = "";
	GetConsoleTitle(szWindowTitle,sizeof(szWindowTitle));

	return szWindowTitle;
}


HWND CConsole::GetHWND()
{
	if(hConsole == NULL) 
		return NULL;

	// try to find our console window and return its HWND
	return FindWindow("ConsoleWindowClass",GetTitle());
}


void CConsole::Show(bool bShow)
{
	CHECK(hConsole);

	// get out console's HWND and show/hide the console
	HWND hWnd = GetHWND();
	if(hWnd != NULL)
		ShowWindow(hWnd, SW_HIDE ? SW_SHOW : bShow);
}


void CConsole::Clear()
{
	CHECK(hConsole);

	/***************************************/
	// This code is from one of Microsoft's
	// knowledge base articles, you can find it at 
	// http://support.microsoft.com/default.aspx?scid=KB;EN-US;q99261&
	/***************************************/

	COORD coordScreen = { 0, 0 };

	BOOL bSuccess;
	DWORD cCharsWritten;
	CONSOLE_SCREEN_BUFFER_INFO csbi; /* to get buffer info */ 
	DWORD dwConSize; 

	/* get the number of character cells in the current buffer */ 

	bSuccess = GetConsoleScreenBufferInfo( hConsole, &csbi );
	ERR(bSuccess);
	dwConSize = csbi.dwSize.X * csbi.dwSize.Y;

	/* fill the entire screen with blanks */ 

	bSuccess = FillConsoleOutputCharacter( hConsole, (TCHAR) ' ', dwConSize, coordScreen, &cCharsWritten );
	ERR(bSuccess);

	/* get the current text attribute */ 

	bSuccess = GetConsoleScreenBufferInfo( hConsole, &csbi );
	ERR(bSuccess);

	/* now set the buffer's attributes accordingly */ 

	bSuccess = FillConsoleOutputAttribute( hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten );
	ERR(bSuccess);

	/* put the cursor at (0, 0) */ 

	bSuccess = SetConsoleCursorPosition( hConsole, coordScreen );
	ERR(bSuccess);
}

void CConsole::Close()
{
	// free the console, now it can't be used anymore until we Create() it again
	FreeConsole();
	hConsole = NULL;
}

#pragma endregion

#pragma region Profiller Definitions

CProfiler::CProfiler(void)
{
}

CProfiler::~CProfiler(void)
{
	// destructor does nothing specific...
}

CProfiler*	CProfiler::GetInstance(void)
{
	static CProfiler instance;
	return &instance;
}

///////////////////////////////////////////////////////////////////////////////////
// ProfileStart
// Starts time count, and adds the function to the profiled
// functions list.
// Params: 
// funcName - the name of the function being profiled
// funcLocation - the location of the function being profiled (ex: winMain)
// profileMultipleTimes - whether the function will be profiled multiple times
//						  useful for functions inside loops.
// numTotalProfileTimesBeforeLog - how many times the function will be profiled
//								   before logging the results.
///////////////////////////////////////////////////////////////////////////////////
void CProfiler::ProfileStart(string funcName, string funcLocation, string notes, bool profileMultipleTimes, int numTotalProfileTimesBeforeLog) // starts profiling
{
	if(!profileMultipleTimes)
	{
		int nID = -1;
		for(int i = (int)m_vProfiledFunctions.size() - 1; i >= 0; --i)
		{
			if(m_vProfiledFunctions[i].funcName == funcName && m_vProfiledFunctions[i].funcLocation == funcLocation)
				nID = i;
		}
		if(nID == -1)
		{
			FUNCTION_PROFILE function;

			function.funcName = funcName;
			function.funcLocation = funcLocation;
			function.notes = notes;
			function.numTimesCalled = 1;
			function.elapsedTimes.push_back(0);
			m_Retval = QueryPerformanceCounter (&function.StartCounter);

			m_vProfiledFunctions.push_back(function);
		}
		else
		{
			m_vProfiledFunctions[nID].numTimesCalled += 1;
			m_vProfiledFunctions[nID].elapsedTimes.push_back(0);
			m_Retval = QueryPerformanceCounter (&m_vProfiledFunctions[nID].StartCounter);
		}
	}
	else
	{
		int index = -1;
		for(int i = (int)(m_vProfiledFunctions.size() - 1); i >= 0; --i)
		{
			if(m_vProfiledFunctions[i].funcName == funcName && m_vProfiledFunctions[i].funcLocation == funcLocation)
			{
				index = i;
				break;
			}
		}
		if(index != -1)
		{
			if(m_vProfiledFunctions[index].numTimesCalled < m_vProfiledFunctions[index].numTotalProfileTimes + 1)
				m_vProfiledFunctions[index].numTimesCalled += 1;

			m_vProfiledFunctions[index].multipleCalls = true;
			m_Retval = QueryPerformanceCounter (&m_vProfiledFunctions[index].StartCounter);
		}
		else
		{
			FUNCTION_PROFILE function;

			function.funcName = funcName;
			function.funcLocation = funcLocation;
			function.notes = notes;
			function.numTimesCalled = 1;
			function.multipleCalls = true;
			function.numTotalProfileTimes = numTotalProfileTimesBeforeLog;
			m_Retval = QueryPerformanceCounter (&function.StartCounter);
			function.elapsedTimes.push_back(0);

			m_vProfiledFunctions.push_back(function);
		}
	}
	// get and store start time
}

///////////////////////////////////////////////////////////////////////////////////
// ProfileEnd 
// End profiling and optionally write a string to the
// debug console or debug output, depending on the
// LOGTYPE specified on Initialize().
// Params:
// funcName - the name of the function being profiled (IT HAS TO BE SAME AS THE
//													   ONE PASSED IN ProfileStart)
// funcLocation- the location of the function being profiled (IT HAS TO BE SAME AS THE
//				 										      ONE PASSED IN ProfileStart)
///////////////////////////////////////////////////////////////////////////////////
void CProfiler::ProfileEnd(string funcName, string funcLocation)
{
	int nID = -1;
	for(int i = (int)m_vProfiledFunctions.size() - 1; i >= 0; --i)
	{
		if(m_vProfiledFunctions[i].funcName == funcName && m_vProfiledFunctions[i].funcLocation == funcLocation)
			nID = i;
	}
	// get and store finishing time and calc elapsed time(ticks)
	m_Retval = QueryPerformanceCounter (&m_EndCounter);
	if(m_vProfiledFunctions[nID].multipleCalls)
	{
		if(m_vProfiledFunctions[nID].numTimesCalled < m_vProfiledFunctions[nID].numTotalProfileTimes)
		{
			m_vProfiledFunctions[nID].elapsedTimes[0] = ( m_EndCounter.QuadPart - m_vProfiledFunctions[nID].StartCounter.QuadPart );
			m_vProfiledFunctions[nID].totalElapsedTime += m_vProfiledFunctions[nID].elapsedTimes[0];
		}

		if(m_vProfiledFunctions[nID].numTimesCalled == m_vProfiledFunctions[nID].numTotalProfileTimes)
			LogResults(nID);
	}
	else
	{
		m_vProfiledFunctions[nID].elapsedTimes[m_vProfiledFunctions[nID].numTimesCalled - 1] = ( m_EndCounter.QuadPart - m_vProfiledFunctions[nID].StartCounter.QuadPart );
		LogResults(nID);
	}
}
///////////////////////////////////////////////////////////////////////////////////
// LogResults 
// Logs results based on the LOGTYPE specified on Initialize().
// Params:
// nID - the location of the function being profiled in the m_vProfiledFunctions
//		 vector.
///////////////////////////////////////////////////////////////////////////////////
void CProfiler::LogResults(int nID)
{
	if (m_LogType == LOGCONSOLE || m_LogType == LOGALL)
	{
		if(m_vProfiledFunctions[nID].multipleCalls)
		{
			console.Color(FOREGROUND_INTENSITY);
			console.Output("Function: ");
			console.Color();
			console.Output(m_vProfiledFunctions[nID].funcName);
			console.Color(FOREGROUND_INTENSITY);
			console.Output();
			console.Output("Location: ");
			console.Color();
			console.Output(m_vProfiledFunctions[nID].funcLocation);
			console.Output();
			console.Color(FOREGROUND_INTENSITY);
			if(m_vProfiledFunctions.back().notes != "")
			{
				console.Output("Notes: ");
				console.Color(FOREGROUND_BLUE);
				console.Output(m_vProfiledFunctions[nID].notes);
				console.Output();
				console.Color(FOREGROUND_INTENSITY);
			}
			console.Output("Ticks: ");
			console.Color(FOREGROUND_GREEN);

			char string_res[24];
			sprintf_s(string_res,"%I64d",m_vProfiledFunctions[nID].elapsedTimes[0]);
			string time(string_res);

			console.Output(time);

			console.Color(FOREGROUND_INTENSITY);
			console.Output(" Secs: ");
			console.Color(FOREGROUND_GREEN);

			sprintf_s(string_res,"%.8f",SecsFromTicks(m_vProfiledFunctions[nID].elapsedTimes[0]));
			string secs(string_res);

			console.Output(secs);

			console.Output();
			console.Color(FOREGROUND_INTENSITY);
			console.Output("Number of times called: ");
			console.Color(FOREGROUND_RED);
			sprintf_s(string_res,"%i", m_vProfiledFunctions[nID].numTimesCalled);
			string num(string_res);
			console.Output(num);
			console.Output();

			console.Color(FOREGROUND_INTENSITY);
			console.Output("Avg Ticks: ");
			console.Color(FOREGROUND_GREEN);

			sprintf_s(string_res,"%I64d", __int64(m_vProfiledFunctions[nID].totalElapsedTime / m_vProfiledFunctions[nID].numTimesCalled));
			string avgTime(string_res);

			console.Output(avgTime);

			console.Color(FOREGROUND_INTENSITY);
			console.Output(" Avg Secs: ");
			console.Color(FOREGROUND_GREEN);

			sprintf_s(string_res,"%.8f",SecsFromTicks(__int64(m_vProfiledFunctions[nID].totalElapsedTime / m_vProfiledFunctions[nID].numTimesCalled)));
			string avgSecs(string_res);

			console.Output(avgSecs);
		}
		else
		{
			console.Color(FOREGROUND_INTENSITY);
			console.Output("Function: ");
			console.Color();
			console.Output(m_vProfiledFunctions[nID].funcName);
			console.Color(FOREGROUND_INTENSITY);
			console.Output();
			console.Output("Location: ");
			console.Color();
			console.Output(m_vProfiledFunctions[nID].funcLocation);
			console.Output();
			console.Color(FOREGROUND_INTENSITY);
			if(m_vProfiledFunctions.back().notes != "")
			{
				console.Output("Notes: ");
				console.Color(FOREGROUND_BLUE);
				console.Output(m_vProfiledFunctions[nID].notes);
				console.Output();
				console.Color(FOREGROUND_INTENSITY);
			}
			char string_res[24];
			console.Color(FOREGROUND_INTENSITY);
			console.Output("Call Number: ");
			console.Color(FOREGROUND_RED);
			sprintf_s(string_res,"%i", m_vProfiledFunctions[nID].numTimesCalled);
			string num(string_res);
			console.Output(num);
			console.Output();

			console.Color(FOREGROUND_INTENSITY);
			console.Output("Ticks: ");
			console.Color(FOREGROUND_GREEN);

			sprintf_s(string_res,"%I64d",m_vProfiledFunctions[nID].elapsedTimes[m_vProfiledFunctions[nID].numTimesCalled - 1]);
			string time(string_res);

			console.Output(time);

			console.Color(FOREGROUND_INTENSITY);
			console.Output(" Secs: ");
			console.Color(FOREGROUND_GREEN);

			sprintf_s(string_res,"%.8f",SecsFromTicks(m_vProfiledFunctions[nID].elapsedTimes[m_vProfiledFunctions[nID].numTimesCalled - 1]));
			string secs(string_res);

			console.Output(secs);
		}

		console.Color();
		console.Output();
		console.Output();
	}
	else if( m_LogType == LOG_DEBUG_OUTPUT)
	{
		if(m_vProfiledFunctions[nID].multipleCalls)
		{
			OutputDebugString("\n");
			OutputDebugString("Function: ");
			OutputDebugString(m_vProfiledFunctions[nID].funcName.c_str());
			OutputDebugString("\n");
			OutputDebugString("Location: ");
			OutputDebugString(m_vProfiledFunctions[nID].funcLocation.c_str());
			OutputDebugString("\n");
			if(m_vProfiledFunctions.back().notes != "")
			{
				OutputDebugString("Notes: ");
				OutputDebugString(m_vProfiledFunctions[nID].notes.c_str());
				OutputDebugString("\n");
			}
			OutputDebugString("Ticks: ");
			char string_res[24];
			sprintf_s(string_res,"%I64d", m_vProfiledFunctions[nID].elapsedTimes[0]);
			OutputDebugString(string_res);
			OutputDebugString(" Secs: ");
			sprintf_s(string_res,"%.8f",SecsFromTicks(m_vProfiledFunctions[nID].elapsedTimes[0]));
			OutputDebugString(string_res);

			OutputDebugString("\n");
			OutputDebugString("Number of times called: ");
			sprintf_s(string_res,"%i", m_vProfiledFunctions[nID].numTimesCalled);
			OutputDebugString(string_res);
			OutputDebugString("\n");

			console.Output("Avg Ticks: ");

			sprintf_s(string_res,"%I64d", __int64(m_vProfiledFunctions[nID].totalElapsedTime / m_vProfiledFunctions[nID].numTimesCalled));

			OutputDebugString(string_res);

			console.Output(" Avg Secs: ");

			sprintf_s(string_res,"%.8f",SecsFromTicks(__int64(m_vProfiledFunctions[nID].totalElapsedTime / m_vProfiledFunctions[nID].numTimesCalled)));

			OutputDebugString(string_res);
		}
		else
		{
			OutputDebugString("\n");
			OutputDebugString("Function: ");
			OutputDebugString(m_vProfiledFunctions[nID].funcName.c_str());
			OutputDebugString("\n");
			OutputDebugString("Location: ");
			OutputDebugString(m_vProfiledFunctions[nID].funcLocation.c_str());
			OutputDebugString("\n");
			if(m_vProfiledFunctions.back().notes != "")
			{
				OutputDebugString("Notes: ");
				OutputDebugString(m_vProfiledFunctions[nID].notes.c_str());
				OutputDebugString("\n");
			}
			char string_res[24];

			OutputDebugString("Call Number: ");
			sprintf_s(string_res,"%i", m_vProfiledFunctions[nID].numTimesCalled);
			OutputDebugString(string_res);
			OutputDebugString("\n");

			OutputDebugString("Ticks: ");
			sprintf_s(string_res,"%I64d", m_vProfiledFunctions[nID].elapsedTimes[m_vProfiledFunctions[nID].numTimesCalled - 1]);
			OutputDebugString(string_res);
			OutputDebugString(" Secs: ");
			sprintf_s(string_res,"%.8f",SecsFromTicks(m_vProfiledFunctions[nID].elapsedTimes[m_vProfiledFunctions[nID].numTimesCalled - 1]));
			OutputDebugString(string_res);
		}

		OutputDebugString("\n\n");
	}

}

// The following function convert ticks to seconds
double CProfiler::SecsFromTicks (__int64 ticks)
{
	return static_cast<double>(ticks) / static_cast<double>(m_QPFrequency.QuadPart);
}

//////////////////////////////////////////////////////////////
// Initialize 
// Sets the logtype and build info. Also clear all variables.
//////////////////////////////////////////////////////////////
void CProfiler::Initialize(string buildInfo, LOGTYPE logtype)
{
	// store logging type
	m_LogType = logtype;
	if(m_LogType == LOGCONSOLE || m_LogType == LOGALL)
	{
		console.Create("Code Pofiler Debug Console");
	}

	m_sBuildInfo = "Build Info: " + buildInfo;

	console.Output(m_sBuildInfo);
	console.Output();
	console.Output("-------------------------------------------------------------------------------");
	console.Output();
	console.Output();

	// members and gets the correct counter frequency
	// for the system.
	ZeroMemory(&m_EndCounter,sizeof(m_EndCounter));
	ZeroMemory(&m_QPFrequency, sizeof(m_QPFrequency));
	m_Retval = 0;
	m_bLogMultipleNow = false;

	// Query Frecuency
	m_Retval = QueryPerformanceFrequency(&m_QPFrequency);	
}

//////////////////////////////////////////////////////////////
// Shutdown 
// Writes the saved profiled functions and the timers to an
// output file if specified by the LOGTYPE.
//////////////////////////////////////////////////////////////
void CProfiler::Shutdown()
{
	if(m_LogType == LOGTEXTOUT || m_LogType == LOGALL)
	{
		std::ofstream ofl;
		ofl.open("Code Profiler Log.txt");

		if(ofl.is_open())
		{
			ofl << m_sBuildInfo.c_str();
			ofl << "\n";
			ofl << "----------------------------------------------------------------------------";
			ofl << "\n\n";

			for(UINT i = 0; i < m_vProfiledFunctions.size() && ofl.good(); ++i)
			{
				if(m_vProfiledFunctions[i].multipleCalls)
				{
					ofl << "Function: ";
					ofl << m_vProfiledFunctions[i].funcName.c_str();
					ofl << "\n";
					ofl << "Location: ";
					ofl << m_vProfiledFunctions[i].funcLocation.c_str();
					ofl << "\n";
					if(m_vProfiledFunctions[i].notes != "")
					{
						ofl << "Notes: ";
						ofl << m_vProfiledFunctions[i].notes.c_str();
						ofl << "\n";
					}
					ofl << "Ticks: ";

					char string_res[24];
					sprintf_s(string_res,"%I64d",m_vProfiledFunctions[i].elapsedTimes[0]);
					ofl << string_res;

					ofl << " Secs: ";

					sprintf_s(string_res,"%.8f",SecsFromTicks(m_vProfiledFunctions[i].elapsedTimes[0]));
					ofl << string_res;

					ofl << "\n";
					ofl << "Number of times called: ";
					sprintf_s(string_res,"%i", m_vProfiledFunctions[i].numTimesCalled - 1);
					ofl << string_res;
					ofl << "\n";

					ofl << "Avg Ticks: ";

					sprintf_s(string_res,"%I64d", __int64(m_vProfiledFunctions[i].totalElapsedTime / (m_vProfiledFunctions[i].numTimesCalled - 1) ));

					ofl << string_res;

					ofl << " Avg Secs: ";

					sprintf_s(string_res,"%.8f",SecsFromTicks(__int64(m_vProfiledFunctions[i].totalElapsedTime / (m_vProfiledFunctions[i].numTimesCalled - 1) )));

					ofl << string_res;
				}
				else
				{
					ofl << "Function: ";
					ofl << m_vProfiledFunctions[i].funcName.c_str();
					ofl << "\n";
					ofl << "Location: ";
					ofl << m_vProfiledFunctions[i].funcLocation.c_str();
					ofl << "\n";
					if(m_vProfiledFunctions[i].notes != "")
					{
						ofl << "Notes: ";
						ofl << m_vProfiledFunctions[i].notes.c_str();
						ofl << "\n";
					}
					for(UINT x = 0; x < m_vProfiledFunctions[i].numTimesCalled; ++x)
					{
						char string_res[24];

						ofl << "Call Number: ";
						sprintf_s(string_res,"%i", x+1);
						ofl << string_res;
						ofl << "\n";

						ofl << "Ticks: ";

						sprintf_s(string_res,"%I64d",m_vProfiledFunctions[i].elapsedTimes[x]);
						ofl << string_res;

						ofl << " Secs: ";

						sprintf_s(string_res,"%.8f",SecsFromTicks(m_vProfiledFunctions[i].elapsedTimes[x]));
						ofl << string_res;
						ofl << "\n";
					}
				}

				ofl << "\n\n";
			}
		}

		ofl.close();
	}

	if(m_LogType == LOGCONSOLE || m_LogType == LOGALL)
	{
		console.Close();
	}
}

#pragma endregion